<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>俄罗斯方块</title>
    <link rel="stylesheet" href="./reset200802.css">
    <link rel="stylesheet" href="./style.css">
</head>
<body onload="init()">
    <div id="container">
    </div>
</body>
<script src="./game_config.js"></script>
<script src="./domHelper.js"></script>
<script>
    // MODEL负责控制活动模型，存有当前活动模型的形状信息
    // BOX负责控制模型所在盒子位置，模型的移动其是对BOX的移动，模型属于模型盒子的一部分，因为没有模型，盒子就没了意义，实际上好像不需要提供盒子这个抽象，使用方只关注模型不关注其他的东西，所以BOX需要提供的API有：创建盒子，盒子移动，旋转模型
    // 单单只有模型是没有意义的，模型和gui是一体的，所以需要提供绘制gui的方法，并在模型变换、移动时同时修改
    
    // 当前活动模型，具体模型元素在一个4*4的盒子里
    var CURRENT_MODEL = {
        _boxPosition: {},
        _model: {
            model: null,
            shapeIndex: null,
            getCurrentShape: function() {return this.model[this.shapeIndex];},
            getSpecificShape: function(shape) {return this.model[shape];},
            getNextClockwiseShape: function() {return (this.shapeIndex + 1 + this.model.length) % this.model.length;},
            getNextCounterClockwiseShape: function() {return (this.shapeIndex - 1 + this.model.length) % this.model.length;},
            rotateClockwise: function() {this.shapeIndex = this.getNextClockwiseShape();},
            rotateCounterClockwise: function() {this.shapeIndex = this.getNextCounterClockwiseShape();},
        },
        createModel: function(modelType, modeShape) {
            this._initBox();
            this._model.model = allModel[modelType];
            this._model.shapeIndex = modeShape;
            domHelper.buildModelElement();
            this._drawModel();
        },
        // 移动模型到指定下标位置
        move: function(x, y) {
            let targetX = this._offsetX(x);
            let targetY = this._offsetY(y);
            this._boxPosition.x = targetX;
            this._boxPosition.y = targetY;
            this._drawModel();
        },
        rotateClockwise: function() {
            this._model.rotateClockwise();
            this._drawModel();
        },
        rotateCounterClockwise: function() {
            this._model.rotateCounterClockwise();
            this._drawModel();
        },
        calCurrentShapeFuturePosition: function(offsetX, offsetY) {
            return this._calFuturePosition(this._model.getCurrentShape(), offsetX, offsetY);
        },
        calTheShapeFuturePosition: function(shapeIndex) {
            return this._calFuturePosition(this._model.getSpecificShape(shapeIndex), 0, 0);
        },
        _calFuturePosition: function(modelShape, offsetX, offsetY) {
            let positions = [];
            for (let i = 0; i < modelShape.indexs.length; i++) {
                let activeEleX = this._offsetX(offsetX + modelShape.indexs[i].x);
                let activeEleY = this._offsetY(offsetY + modelShape.indexs[i].y);
                positions.push({x: activeEleX, y: activeEleY});
            }
            return positions;
        },
        // 固定当前活动模型
        fixActiveModel: function() {
            let activeEles = domHelper.getActiveElements();
            let modelShape = this._model.getCurrentShape();
            // 更新固定模型占用的下标并设置属性
            for (let i = 0; i < activeEles.length; i++) {
                let activeEleX = this._offsetX(modelShape.indexs[i].x);
                let activeEleY = this._offsetY(modelShape.indexs[i].y);
                boardManager.addFixedElement(activeEles[i], activeEleX, activeEleY);
            }
            boardManager.tryClearRow();
        },
        _initBox: function() {this._boxPosition.x = 0; this._boxPosition.y = 0;},
        _drawModel: function() {
            let modelShape = this._model.getCurrentShape();
            let positions = [];
            for (let i = 0; i < modelShape.indexs.length; i++) {
                positions.push({x: this._offsetX(modelShape.indexs[i].x), y: this._offsetY(modelShape.indexs[i].y)});
            }
            domHelper.updateActiveElementPosition(positions);
        },
        _offsetX: function(x) {return this._boxPosition.x + x;},
        _offsetY: function(y) {return this._boxPosition.y + y;},
        _updateBoxPosition: function(x, y) {this._boxPosition.x = x; this._boxPosition.y = y;},
    }

     function getRandomInt(topBorder) {
        return Math.round(Math.random() * topBorder);
    }

    // 存放已固定模型的下标信息
    var boardManager = {
        indexs: new Array(GAME_BOX_SIZE.row),
        getSpecificFixedElement: function(i, j) {return document.querySelector(`.${CLASS_FIXED_MODEL}[${ATTR_DATA_INDEX}="${this.generateDataIndexValue(i, j)}"]`);},
        addFixedElement: function(ele, i, j) {
            if (!this.indexs[i]) {
                this.initRow(i);
            }
            this.indexs[i][j] = 1;
            ele.setAttribute(ATTR_DATA_INDEX, this.generateDataIndexValue(i, j));
            domHelper.switch2Fixed(ele);
        },
        occupied: function(i, j) {return this.indexs[i] && this.indexs[i][j]},
        initRow: function(row) {return this.indexs[row] = new Array(GAME_BOX_SIZE.col);},
        isInitedRow: function(row) {return this.indexs[row];},
        clearRow: function(row) {return this.indexs[row] = undefined;},
        generateDataIndexValue: (i, j) => `${i},${j}`,
        tryClearRow: function() {
            // 清理完整的一行
            // 标记清理的最底下的行下标
            let lowestRow = -1;
            for (let i = this.indexs.length - 1; i >= 0; i--) {
                if (!this.isInitedRow(i)) {
                    break;
                }
                let shouldClear = true;
                for (let j = 0; j < this.indexs[i].length; j++) {
                    if (!this.occupied(i, j)) {
                        // 说明有空位，当前行就不需要进行清理
                        shouldClear = false;
                        break;
                    }
                }
                if (shouldClear) {
                    if (lowestRow == -1) {
                        lowestRow = i;
                    }
                    // 删除当前行模型块
                    for (let j = 0; j < this.indexs[i].length; j++) {
                        let ele = this.getSpecificFixedElement(i, j);
                        ele.remove();
                    }
                    this.clearRow(i);
                }
            }
            // 下沉其他行到空行
            for (let i = lowestRow - 1; lowestRow != -1 && i >= 0; i--) {
                if (this.isInitedRow(i + 1) || !this.isInitedRow(i)) {
                    continue;
                }
                // 如果下一行是undefined并且当前行存在模型块，就下沉当前行到最下一行undefined的位置
                // 找到最下一行undefined的位置
                let targetRow = -1;
                for (let r = i + 1; r <= lowestRow; r++) {
                    if (this.isInitedRow(r)) {
                        break;
                    }
                    targetRow = r;
                }
                for (let j = 0; j < this.indexs[i].length; j++) {
                    if (!this.occupied(i, j)) {
                        continue;
                    }
                    let ele = this.getSpecificFixedElement(i, j);
                    domHelper.updateElementPosition(ele, targetRow, j);
                    this.addFixedElement(ele, targetRow, j);
                }
                this.clearRow(i);
            }
        }
    }

    function init() {
        console.log("开始初始化游戏界面");
        initGameBox();
        console.log("开始注册键盘按键监听");
        listenKeyBoard();
        console.log("注册键盘按键监听完成");
        console.log("开始创建模型");
        CURRENT_MODEL.createModel(getRandomInt(6), 0);
        setIntervalDescend();
    }

    function initGameBox() {
        domHelper.updateContainerSize(GAME_BOX_SIZE.col, GAME_BOX_SIZE.row);
    }

    // 注册按键监听器
    function listenKeyBoard() {
        document.onkeydown = event => {
            console.log("keycode=", event.keyCode);
            switch (event.keyCode) {
                case 40:
                    if (isMoveOutOfBorder(1, 0)) {
                        return;
                    }
                    console.log("下");
                    CURRENT_MODEL.move(1, 0);
                    break;
                case 37:
                    if (isMoveOutOfBorder(0, -1)) {
                        return;
                    }
                    console.log("左");
                    CURRENT_MODEL.move(0, -1);
                    break;
                case 39:
                    if (isMoveOutOfBorder(0, 1)) {
                        return;
                    }
                    console.log("右");
                    CURRENT_MODEL.move(0, 1);
                    break;
                case 90:
                    if (shapeCounterClockwiseChangeOutOfBorder()) {
                        return;
                    }
                    CURRENT_MODEL.rotateCounterClockwise();
                    break;
                case 88:
                    if (shapeClockwiseChangeOutOfBorder()) {
                        return;
                    }
                    CURRENT_MODEL.rotateClockwise();
                    break;
            }
        };
    }

    function setIntervalDescend() {
        setInterval(() => {
            if (isMoveOutOfBorder(1, 0)) {
                return;
            }
            CURRENT_MODEL.move(1, 0);
        }, 1000);
    }

    function isMoveOutOfBorder(x, y) { 
        if (isOutOfBottomForMove(x, y)) {
            CURRENT_MODEL.fixActiveModel();
            CURRENT_MODEL.createModel(getRandomInt(6), 0);
            return true;
        }
        if (isOutOfSideForMove(x, y)) {
            return true;
        }
        return false;
    }

    function shapeClockwiseChangeOutOfBorder() {
        let nextShapeIndex = CURRENT_MODEL._model.getNextClockwiseShape();
        return isOutOfBorder(nextShapeIndex);
    }

    function shapeCounterClockwiseChangeOutOfBorder() {
        let nextShapeIndex = CURRENT_MODEL._model.getNextCounterClockwiseShape();
        return isOutOfBorder(nextShapeIndex);

    }

    // 检查是否越界
    function isOutOfBorder(nextModelShapeIndex) {
        return isOutOfSideForShapeChange(nextModelShapeIndex) || isOutOfBottomForShape(nextModelShapeIndex);
    }

    function isOutOfSideForShapeChange(shapeIndex) {
        let positions = CURRENT_MODEL.calTheShapeFuturePosition(shapeIndex);
        return checkSide(positions);
    }

    function isOutOfSideForMove(modelOffsetX, modelOffsetY) {
        let positions = CURRENT_MODEL.calCurrentShapeFuturePosition(modelOffsetX, modelOffsetY);
        return checkSide(positions);
    }

    function checkSide(positions) {
        for (let i = 0; i < positions.length; i++) {
            if (boardManager.occupied(positions[i].x, positions[i].y)) {
                return true;
            }
            if (positions[i].y < 0 || positions[i].y >= GAME_BOX_SIZE.col) {
                return true;
            }
        }
        return false;
    }

    function isOutOfBottomForShape(nextModelShapeIndex) {
        let positions = CURRENT_MODEL.calTheShapeFuturePosition(nextModelShapeIndex);
        return isOutOfBottom(positions);

    }

    function isOutOfBottomForMove(modelOffsetX, modelOffsetY) {
        let positions = CURRENT_MODEL.calCurrentShapeFuturePosition(modelOffsetX, modelOffsetY);
        return isOutOfBottom(positions);
    }

    function isOutOfBottom(positions) {
        for (let i = 0; i < positions.length; i++) {
            if (boardManager.occupied(positions[i].x, positions[i].y)) {
                return true;
            }
            if (positions[i].x >= GAME_BOX_SIZE.row) {
                return true;
            }
        }
        return false;
    }


</script>
</html>